<?php
/*
 * @Author: 李红雨 - RainLee <rainlee1990@yeah.net>
 * @Date: 2022-03-09 14:14:45
 * @LastEditors: 李红雨 - RainLee <rainlee1990@yeah.net>
 * @LastEditTime: 2022-04-13 17:53:07
 * @Description: File Description
 */

namespace rainlee\auth\guard;

use rainlee\auth\provider\CreateUserProviders;
use rainlee\auth\Recaller;
use think\facade\Cookie;
use think\facade\Session;
use rainlee\auth\Authenticatable;
use rainlee\auth\Policies;
use think\facade\Request;
use think\helper\Str;

class SessionGuard
{

    use CreateUserProviders;

    protected $provider = null;

    protected $policies = null;

    protected $ignored = [];

    protected $guardName;

    protected $user = null;

    protected $recallAttempted = false;


    public function __construct($name, $config)
    {
        $this->guardName = $name;
        $this->ignored = $config['ignored'] ?? [];
        $this->policies = $config['policies'] ?? null;
        $this->provider = $this->createUserProviders($config['provider']);
    }

    /**
     * 尝试登录
     *
     * @param  array  $credentials
     * @param  bool   $remember
     * @return bool
     */
    public function attempt(array $credentials = [], $remember = false)
    {
        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
        if (!is_null($user) && $this->hasValidCredentials($user, $credentials)) {
            $this->login($user, $remember);
            return true;
        }

        return false;
    }

    /**
     * 尝试通过ID登录
     * 
     * @param int $id
     * @param bool $remember
     * @return bool
     */
    public function attemptById($id, $remember = false)
    {
        if (!is_null($user = $this->provider->retrieveById($id))) {
            $this->login($user, $remember);
            return true;
        }
        return false;
    }

    /**
     * 验证参数有效性
     * 
     * @param \rainlee\auth\Authenticatable|null $user 用户对象
     * @param array $credentials 参数
     * @return bool
     */
    protected function hasValidCredentials($user, $credentials)
    {
        return !is_null($user) && $this->provider->validatePassword($user, $credentials['password']);
    }

    /**
     * 登录操作
     * 
     * @param \rainlee\auth\Authenticatable $user 用户对象
     * @param bool $remember
     * @return void
     */
    protected function login(Authenticatable $user, $remember = false)
    {
        Session::set($this->getName(), $user->getAuthIdentifier());

        if ($remember) {
            $this->createRememberToken($user);
            $this->refreshRecallerCooke($user);
        }

        $this->setUser($user);
    }

    /**
     * 创建Remember Token并保存
     * 
     * @param \rainlee\auth\Authenticatable $user 用户对象
     * @return void
     */
    protected function createRememberToken(Authenticatable $user)
    {
        $user->setRememberToken($token = Str::random(60));
        $this->provider->updateRememberToken($user, $token);
    }

    /**
     * 刷新Remember Cookie
     * 
     * @param \rainlee\auth\Authenticatable $user 用户对象
     * @return void
     */
    protected function refreshRecallerCooke($user)
    {
        Cookie::forever(
            $this->getRecallerName(),
            (new Recaller())->encrype($user->getAuthIdentifier() . '|' . $user->getRememberToken())
        );
    }

    /**
     * 获取用户信息
     */
    public function user()
    {
        // 用户存在则只接返回用户信息
        if (!is_null($this->user)) {
            return $this->user;
        }

        // 判断SESSION是否有用户标识，如果没有则尝试获取remember cookie
        $id = Session::get($this->getName());
        if (!is_null($id)) {
            $this->user = $this->provider->retrieveById($id);
        }

        $recaller = $this->recaller();

        if (is_null($this->user) && !is_null($recaller)) {
            $this->user = $this->userFromRecaller($recaller);

            if ($this->user) {
                Session::set($this->getName(), $this->user->getAuthIdentifier());
            }
        }

        return $this->user;
    }

    /**
     * 通过remember token获取用户信息
     *
     * @param  \rainlee\auth\Recaller $recaller
     * @return mixed
     */
    protected function userFromRecaller($recaller)
    {
        if (!$recaller->valid() || $this->recallAttempted) {
            return;
        }

        $this->recallAttempted = true;

        $this->viaRemember = !is_null($user = $this->provider->retrieveByRememberToken(
            $recaller->id(),
            $recaller->token()
        ));

        return $user;
    }

    /**
     * 获取并解密remember token的cookie
     *
     * @return \rainlee\auth\Recaller|null
     */
    protected function recaller()
    {
        if ($recaller = Cookie::get($this->getRecallerName())) {
            return (new Recaller())->decrypt($recaller);
        }
        return;
    }

    /**
     * 验证用户是否登录
     *
     * @return bool
     */
    public function check()
    {
        return !is_null($this->user());
    }

    /**
     * 权限认证
     *
     * @return \rainlee\auth\Policies
     */
    public function authorization()
    {
        if (is_null($this->policies)) {
            return;
        }
        return new Policies($this->policies, $this->user());
    }

    /**
     * 判断是否为无需认证的节点
     */
    public function isIgnoredNode($node)
    {
        if (in_array($node, $this->ignored)) {
            return true;
        }
        return false;
    }

    /**
     * 退出登录
     *
     * @return void
     */
    public function logout()
    {
        $user = $this->user();

        $this->clearUserDataFromStorage();

        if (!is_null($user)) {
            $this->clearRememberToken($user);
        }

        $this->user = null;

        $this->loggedOut = true;
    }

    /**
     * 清除登录SESSION和COOKIE
     * 
     * @return void
     */
    protected function clearUserDataFromStorage()
    {
        Session::delete($this->getName());
        if ($this->recaller()) {
            Cookie::delete($this->getRecallerName());
        }
    }

    /**
     * 清除数据库中的remember token
     * 
     * @return void
     */
    protected function clearRememberToken($user)
    {
        $this->provider->updateRememberToken($user, '');
    }

    /**
     * 获取储存用户信息的session名
     *
     * @return string
     */
    protected function getName()
    {
        return 'login_' . $this->guardName . '_' . sha1(static::class);
    }

    /**
     * 获取储存"recaller"的cookie名
     *
     * @return string
     */
    protected function getRecallerName()
    {
        return 'remember_' . $this->guardName . '_' . sha1(static::class);
    }

    /**
     * 设置当前用户
     * 
     * @param \rainlee\auth\Authenticatable $user
     * @return void
     */
    protected function setUser(Authenticatable $user)
    {
        $this->user = $user;
    }
}
